package fr.lelouet.monitoring.stress


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * @author lelouet
 *
 */
class DiskStress implements Runnable{
	
	private static final Logger logger = LoggerFactory.getLogger(DiskStress.class)
	
	protected boolean hasToStop=false
	
	String fileName=".tmpDiskStress"
	
	/** the size of each block to write to the file. set to empty String to stop loading the system*/
	String blocSize="1k"
	
	/** the number of blocs to write before syncing the file system*/
	int blocCount=1
	
	public long stressFileSize() {
		return DiskTools.byteswritten(blocSize, blocCount)
	}
	
	/** make this write files to the disk. the parameters modifications are understood each time a new write is done*/
	public void run() {
		hasToStop=false
		while(!hasToStop) {
			actionId++
			if(actionId>100000) { actionId=0
			}
			if(blocSize?.size() == 0 || blocCount==0) {
				Thread.sleep(1)//we don't want it to be put back on the active threads list right now.
			} else {
				DiskTools.asyncStress(fileName, blocCount, blocSize)
			}
		}
	}
	
	protected long seqAwaitings = 0
	
	/** the Id of the last action. When it changes, that means another loop has started*/
	protected long actionId=0
	
	/** ensure another internal loop is started. This is needed when changing the configuration, since that change may be applied to already running loop*/
	public void waitNextLoop() {
		long lastAction=actionId
		while(actionId==lastAction) {
			Thread.sleep(1)
		}
	}
	
	public void stop() {
		hasToStop=true
	}
	
	double minBlocSizeCoeff = 1.05
	
	double minBlocSizeCalibration = 1
	
	String blocSizeCalibrationSuffix="k"
	
	long calibrationSleepMS = 100
	
	long maxFileSize = 1024*1024*100
	
	/** Test the write performance with present parameters.
	 * Has to be called by an already running DiskTest
	 * @param calibrationEvaluationTimeMS the number of milliseconds to wait between two system probes
	 * @return the number of Mbytes written to the disk per second
	 */
	protected double evaluateWritePerf(long calibrationEvaluationTimeMS) {
		long lastnbwrites = DiskTools.systemWrites()
		Thread.sleep calibrationEvaluationTimeMS
		long diffwrites =  DiskTools.systemWrites()-lastnbwrites
		double result = diffwrites/calibrationEvaluationTimeMS
		logger.debug("bloc size {} evaluated to {}", blocSize, result)
		return result
	}
	
	public List<String> calibrateBlocSize(long calibrationEvaluationTimeMS = 500 ) {
		List<String> ret = [""]
		DiskStress stress = this
		DiskTools.systemSync()
		Thread.start { stress.run() }
		String lastparam=""
		double bestVal = 0
		int paramBlocSize = minBlocSizeCalibration
		
		int pass=0
		while(pass<2 && stressFileSize()<=maxFileSize) {
			setBlocSize ( paramBlocSize.toString()+blocSizeCalibrationSuffix )
			if(stressFileSize()<=maxFileSize) {
				waitNextLoop()
				Thread.sleep(calibrationSleepMS)
				double eval = evaluateWritePerf (calibrationEvaluationTimeMS)
				if(bestVal!=0){
					double coeff = (eval)/bestVal
					if(coeff>=minBlocSizeCoeff) {
						ret.add (lastparam)
						ret.add  getBlocSize()
						pass++
					} else {
					}
				}
				if(eval>bestVal) {bestVal = eval
				}
				lastparam = getBlocSize()
			}
			paramBlocSize *=2
		}
		logger.trace("quitting : blocsize={}, size={}, pass={}", getBlocSize(), stressFileSize(), pass)
		
		int fail=0
		while(fail<2 && stressFileSize()<=maxFileSize) {
			setBlocSize( paramBlocSize.toString()+blocSizeCalibrationSuffix )
			if(stressFileSize()<=maxFileSize) {
				waitNextLoop()
				Thread.sleep(calibrationSleepMS)
				double eval = evaluateWritePerf (calibrationEvaluationTimeMS)
				double coeff = eval/bestVal
				if(coeff<minBlocSizeCoeff) {
					fail++
				} else {
					fail=0
					ret.add getBlocSize()
				}
				if(eval>bestVal) {bestVal = eval
				}
			}else {
				logger.debug("blocsize {} too high", getBlocSize())
			}
			paramBlocSize *=2
		}
		logger.trace("quitting : blocsize={}, size={}, fail={}", getBlocSize(), stressFileSize(), fail)
		stress.stop()
		return ret
	}
	
	static void main (args) {
		DiskStress ds = new DiskStress()
		println "calibrating"
		def calibration = ds.calibrateBlocSize(500)
		calibration.each{ print " $it" }
		println "\nrunning"
		Thread.start { ds.run() }
		calibration.each{
			println "block size $it"
			ds.setBlocSize it
			ds.waitNextLoop()
			Thread.sleep 1000
			4.times {println("  -> "+ds.evaluateWritePerf(500)) }
		}
		ds.stop()
	}
}
